<?php
namespace service;
use service\nearby\Point;

/**
 * Class Nearby 搜索附近坐标点
 * @package service
 */
class Nearby{
    public static $client = null;
    public $key = null;
    public $port= 6379;
    public $size = 0;
    public $members = [];
    private $isDB = true;
    /**
     * Nearby constructor.
     * @param null $host
     * @param null $port
     * @param null $key
     * @param null $timestamp
     */
    public function __construct($host=null,$port=null,$key=null,$timestamp=null)
    {
        $redis = new \Redis();
        $host = $host ?? '127.0.0.1';
        $this->key = $key ?? 'cities:locations';
        $this->port = $port ?? 6379;
        $redis->connect($host,$this->port);
        if($timestamp){
            $redis->expireAt($this->key, $timestamp);
        }
        self::$client = $redis;
    }

    /**
     * @param bool $db
     * @return $this
     */
    public function setIsDB($db=false){
        $this->isDB = $db;
        return $this;
    }

    /**
     * 设置过期时间
     * @param $expire_timestamp
     * @return Nearby
     */
    public function setExpireTimestamp($expire_timestamp): Nearby
    {
//        self::$client->expireAt($this->key,$expire_timestamp);
        return $this;
    }

    /**
     * @param Point $point
     * @return Nearby
     */
    public function addPos(Point $point): Nearby
    {
        $i = $this->size();
        $_i = self::$client->rawCommand('geoadd', $this->key, $point->lng,$point->lat,$point->member);
        if($_i){
            array_push($this->members,$point);
            $this->size = $i + $_i;
        }
        if($this->isDB){
            $this->save($point);
        }
        $this->all();
        return $this;
    }

    /**
     * 添加Pos
     * @param array $arr Pos
     * @return Nearby
     */
    public function addPos1(Array $arr): Nearby
    {
        $point = new Point($arr['lng'],$arr['lat'],$arr['member'],0,$arr['id']);
        $i = $this->size();
        $_i = self::$client->rawCommand('geoadd', $this->key, $point->lng,$point->lat,$point->member);
        if($_i){
            array_push($this->members,$point);
            $this->size = $i + $_i;
            $data = json_encode($point);
            if($this->isDB){
                $this->save($data);
            }
        }
        $this->all();
        return $this;
    }

    /**
     * 添加Pos
     * @param array $points Point 数组
     * @return Nearby
     */
    public function addPosList($points=[]): Nearby
    {
        $i = $this->size();
        foreach ($points as $v){
            $i += self::$client->rawCommand('geoadd', $this->key, $v->lng,$v->lat,$v->member);
            array_push($this->members,$v);
        }
        if($this->isDB){
            $this->saveAll($points);
        }
        $this->all();
        $this->size = $i;
        return $this;
    }

    /**
     * 添加节点
     * @param array $arr
     * @return Nearby
     */
    public function addPosList1($arr=[]): Nearby
    {
        $points = [];
        foreach ($arr as $v){
            $point = new Point($v['lng'],$v['lat'],$v['member'],0,$v['id']);
            array_push($points,$point);
        }
        $i = $this->size();
        foreach ($points as $v){
            $i += self::$client->rawCommand('geoadd', $this->key, $v->lng,$v->lat,$v->member);
            array_push($this->members,$v);
        }
        if($this->isDB){
            $this->saveAll($points);
        }
        $this->all();
        $this->size = $i;
        return $this;
    }

    /**
     * 测算距离
     * @param $member1
     * @param $member2
     * @param string $unit
     * @return array
     */
    public function getDistanceByMember($member1,$member2,$unit='m'): array
    {
        $distance = self::$client->rawCommand('geodist', $this->key, $member1, $member2,$unit);
        $point1 = $this->getLocationByMember($member1);
        $point2 = $this->getLocationByMember($member2);
        return [
            'point1'=>$point1,
            'point2'=>$point2,
            'distance'=>round($distance,2) . $unit
        ];
    }

    /**
     * 测算距离
     * @param Point $point1
     * @param Point $point2
     * @param string $unit
     * @return array
     */
    public function getDistanceByPoint(Point $point1,Point $point2,$unit='m'): array
    {
        $distance = self::$client->rawCommand('geodist', $this->key, $point1->member, $point2->member,$unit);
        return [
            'point1'=>$point1,
            'point2'=>$point2,
            'distance'=>round($distance,2) . $unit
        ];
    }

    /**
     * @param $member
     * @return array
     */
    public function getHashByMember($member): array
    {
        return self::$client->rawCommand('geohash', $this->key, $member);
    }

    /**
     * @param $member
     * @return Point
     */
    public function getLocationByMember($member): Point
    {
        $location = self::$client->rawCommand('geopos', $this->key, $member)[0];
        $point = new Point($location[0],$location[1],$member);
        return $point;
    }

    /**
     * @param Point $radius
     * @param int $diameter
     * @param string $unit
     * @return array
     */
    public function getPosMemberByRadius(Point $radius,$diameter=1,$unit='km'): array
    {
        $points = self::$client->rawCommand('georadius', $this->key,$radius->lng,$radius->lat,$diameter,$unit,'WITHCOORD','WITHDIST','WITHHASH');
        return self::format($points);
    }

    /**
     * @param $member
     * @param int $diameter
     * @param string $unit
     * @return mixed
     */
    public function getPosMemberByMember($member,$diameter=1,$unit='km')
    {
        $members = self::$client->rawCommand('georadiusbymember', $this->key,$member, $diameter, $unit,'WITHCOORD','WITHDIST','WITHHASH');
        return self::format($members);
    }

    /**
     * @return int
     */
    public function size(): int
    {
        $size = intval(self::$client->zcard($this->key));
        $this->size = $size;
        return $size;
    }

    public function clear(): int
    {
        $i = 0;
        $list = self::$client->zRange($this->key, 0, -1);
        foreach ($list as $v) {
            $i += self::$client->zRem($this->key,$v);
        }
        db('location')->whereIn('member',$list)->delete();
        return $i;
    }

    public function all(): array
    {
        $members = self::$client->zRange($this->key, 0, -1);
        $list = db('location')
            ->field(['status','create_time','update_time'],true)
            ->where('status','eq',1)
            ->whereIn('member',$members)->select();
        $this->members = $list;
        return $this->members;
    }


    public function remove($member): int
    {
        $this->size();
        $result = self::$client->zRem($this->key,$member);
        db('location')->where('member','eq',$member)->delete();
        $this->size--;
        return $result;
    }

    /**
     * @param $point
     */
    private function save($point)
    {
        $point = json_decode(json_encode($point), true);
        $point['create_time'] = $point['update_time'] = time();
        unset($point['distance']);
        unset($point['id']);
        db('location')->insert($point);
    }

    /**
     * @param $arr
     */
    private function saveAll($arr)
    {
        $_data = [];
        $arr = json_decode(json_encode($arr),true);
        foreach ($arr as $v) {
            $v['create_time'] = $v['update_time'] = time();
            unset($v['distance']);
            unset($v['id']);
            array_push($_data,$v);
        }
        db('location')->insertAll($_data);
    }

    /**
     * @param $data
     * @return array
     */
    private static function format($data){
        $_result = [];
        foreach ($data as $k => $v) {
            $_result[$k] = new Point($v[3][0],$v[3][1],$v[0],$v[1]);
        }
        return $_result;
    }
}